package nus.iss.ga;

import java.util.ArrayList;
import java.util.List;

import nus.iss.ga.controller.common.IGAConsole;
import nus.iss.ga.controller.common.IGAController;
import nus.iss.ga.controller.model.Container;
import nus.iss.ga.controller.model.ContainerFitnessFunction;

import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.DeltaFitnessEvaluator;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.IntegerGene;
import org.jgap.impl.SwappingMutationOperator;

/**
 * This is the controller class which responsible of setting GA's parameter, and
 * running of GA.
 * 
 * @author Fum Kok Siong
 * 
 */
public class GAController implements IGAController {

	private int numOfS = 4; // number of vertical layer(in pareller to user's
							// perspective)
	private int numOfC = 4; // number of vertical layer(90 degree angle to
							// user's perspective)
	private int numOfR = 4; // number of container layers
	private int populationSize = 50;
	private double crossoverRate = 0.35d;
	private int mutationRate = 12;
	private int numOfEvolution = 200000;
	private double averageWeight = 0.0d;
	private IChromosome fittest;
	private double penaltyForHeavierAtTop = 2.0d;
	private double penaltyForItemType = 1.0d;
	private double penaltyForItemState = 1.0d;
	private double penaltyForDestination = 2000.0d;
	private boolean isStopped = false;

	private List<Container> list = new ArrayList<Container>();
	private final IGAConsole console;

	/**
	 * Constructor
	 */
	public GAController(IGAConsole console) {
		super();
		this.console = console;
	}

	@Override
	public void setContainerInList(List<Container> list) {
		this.list = new ArrayList(list);
	}

	/**
	 * Sets up the configuration for the problem.
	 * 
	 * @return configured genotype
	 * 
	 * @throws Exception
	 * 
	 * @author Klaus Meffert
	 * @since 3.2
	 */
	protected Genotype configureJGAP() throws Exception {
		double val = 0.0d;
		for (Container container : list) {
			val += container.getWeight();
		}
		averageWeight = val / list.size();
		Configuration gaConf = new DefaultConfiguration();
		gaConf.resetProperty(Configuration.PROPERTY_FITEVAL_INST);
		gaConf.setFitnessEvaluator(new DeltaFitnessEvaluator());
		// Just use a swapping operator instead of mutation and others.
		// ------------------------------------------------------------
		gaConf.getGeneticOperators().clear();
		SwappingMutationOperator swapper = new SwappingMutationOperator(gaConf, mutationRate);
		gaConf.addGeneticOperator(swapper);
		// Setup some other parameters.
		// ----------------------------
		gaConf.setPreservFittestIndividual(true);
		gaConf.setKeepPopulationSizeConstant(true);
		// Set number of individuals (=tries) per generation.
		// --------------------------------------------------
		gaConf.setPopulationSize(populationSize);
		int chromeSize = list.size();

		Genotype genotype = null;
		try {
			// Setup the structure with which to evolve the
			// solution of the problem.
			// --------------------------------------------
			IChromosome sampleChromosome = new Chromosome(gaConf, new IntegerGene(gaConf), chromeSize);
			gaConf.setSampleChromosome(sampleChromosome);
			// Setup the important fitness function!
			// -------------------------------------
			ContainerFitnessFunction fitnessFunction = new ContainerFitnessFunction(this, list);
			fitnessFunction.setAverageWeight(averageWeight);
			fitnessFunction.setPenaltyForDestination(penaltyForDestination);
			fitnessFunction.setPenaltyForHeavierAtTop(penaltyForHeavierAtTop);
			fitnessFunction.setPenaltyForItemState(penaltyForItemState);
			fitnessFunction.setPenaltyForItemType(penaltyForItemType);

			gaConf.setFitnessFunction(fitnessFunction);
			//
			genotype = Genotype.randomInitialGenotype(gaConf);
			// Now ensure that each number from 1..64 (representing the
			// indices of the vents) is represented by exactly one gene.
			// --> Suboptimal here, as randomized initialization becomes
			// obsolete (other solution would be more complicated).
			// ---------------------------------------------------------
			List chromosomes = genotype.getPopulation().getChromosomes();
			for (int i = 0; i < chromosomes.size(); i++) {
				IChromosome chrom = (IChromosome) chromosomes.get(i);
				for (int j = 0; j < chrom.size(); j++) {
					Gene gene = (Gene) chrom.getGene(j);
					gene.setAllele(new Integer(j));
				}
			}
		} catch (InvalidConfigurationException e) {
			e.printStackTrace();
			System.exit(-2);
		}
		return genotype;
	}

	private void doEvolution(Genotype a_genotype) {
		int progress = 0;
		int percentEvolution = numOfEvolution / 100;
		for (int i = 0; i < numOfEvolution; i++) {
			if (isStopped) {
				break;
			}
			a_genotype.evolve();
			// Print progress.
			// ---------------
			if (percentEvolution > 0 && i % percentEvolution == 0) {
				progress++;
				IChromosome fittest = a_genotype.getFittestChromosome();
				double fitness = fittest.getFitnessValue();
				System.out.println("Currently best solution has fitness " + fitness);
				console.outputToConsole(String.format("(%d)Currently best solution has fitness %f", progress ,fitness));
				// printSolution(fittest);
			}
		}
		// Print summary.
		// --------------
		fittest = a_genotype.getFittestChromosome();
		console.outputToConsole("Best solution has fitness " + fittest.getFitnessValue());
		console.outputToFitnessValue(fittest.getFitnessValue());
		System.out.println("Best solution has fitness " + fittest.getFitnessValue());
	}

	@Override
	public void setNumOfEvolutionInThousand(int numOfEvolutionInThousand) {
		this.numOfEvolution = numOfEvolutionInThousand * 1000;
	}

	@Override
	public int getNumOfEvolutionInThousand() {
		return numOfEvolution / 1000;
	}

	@Override
	public int getNumberOfS() {
		return numOfS;
	}

	@Override
	public void setNumberOfS(int numberOfS) {
		this.numOfS = numberOfS;
	}

	@Override
	public int getNumberOfC() {
		return numOfC;
	}

	@Override
	public void setNumberOfC(int numberOfC) {
		this.numOfC = numberOfC;
	}

	@Override
	public int getNumberOfR() {
		return numOfR;
	}

	@Override
	public void setNumberOfR(int numberOfR) {
		this.numOfR = numberOfR;
	}

	@Override
	public boolean runGA() {
		this.isStopped = false;
		Genotype genotype;
		try {
			genotype = configureJGAP();
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		doEvolution(genotype);
		return true;
	}

	@Override
	public List<Container> getResults() {
		List<Container> newList = new ArrayList<Container>();
		for (int i = 0; i < 64; i++) {
			IntegerGene containerIndex = (IntegerGene) fittest.getGene(i);
			Container c = (Container) list.get(containerIndex.intValue());
			newList.add(c);
		}

		return newList;
	}

	@Override
	public double getPenaltyForHeavierAtTop() {
		return penaltyForHeavierAtTop;
	}

	@Override
	public void setPenaltyForHeavierAtTop(double penaltyForHeavierAtTop) {
		this.penaltyForHeavierAtTop = penaltyForHeavierAtTop;
		console.outputToConsole("Penalty(weight) : " + penaltyForHeavierAtTop);
	}

	@Override
	public double getPenaltyForItemType() {
		return penaltyForItemType;
	}

	@Override
	public void setPenaltyForItemType(double penaltyForItemType) {
		this.penaltyForItemType = penaltyForItemType;
		console.outputToConsole("Penalty(type)" + penaltyForItemType);
	}

	@Override
	public double getPenaltyForItemState() {
		return penaltyForItemState;
	}

	@Override
	public void setPenaltyForItemState(double penaltyForItemState) {
		this.penaltyForItemState = penaltyForItemState;
		console.outputToConsole("Penalty(state)" + penaltyForItemState);
	}

	@Override
	public double getPenaltyForDestination() {
		return penaltyForDestination;
	}

	@Override
	public void setPenaltyForDestination(double penaltyForDestination) {
		this.penaltyForDestination = penaltyForDestination;
		console.outputToConsole("Penalty(destination)" + penaltyForDestination);
	}

	@Override
	public double getCrossoverRate() {
		return crossoverRate;
	}

	@Override
	public void setCrossoverRate(double crossoverRate) {
		this.crossoverRate = crossoverRate;
	}

	@Override
	public int getMutationRate() {
		return mutationRate;
	}

	@Override
	public void setMutationRate(int mutationRate) {
		this.mutationRate = mutationRate;
		console.outputToConsole("Mutation Rate(1/X): " + mutationRate);
	}

	@Override
	public int getPopulationSize() {
		return populationSize;
	}

	@Override
	public void setPopulationSize(int populationSize) {
		this.populationSize = populationSize;
		console.outputToConsole("Population size:" + populationSize);
	}

	@Override
	public void stop() {
		this.isStopped = true;

	}

}
